/*

This file is part of Ext JS 4

Copyright (c) 2011 Sencha Inc

Contact:  http://www.sencha.com/contact

Commercial Usage
Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.

If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.

*/
/**
 * @class Ext.resizer.Resizer
 * <p>Applies drag handles to an element or component to make it resizable. The
 * drag handles are inserted into the element (or component's element) and
 * positioned absolute.</p>
 *
 * <p>Textarea and img elements will be wrapped with an additional div because
 * these elements do not support child nodes. The original element can be accessed
 * through the originalTarget property.</p>
 *
 * <p>Here is the list of valid resize handles:</p>
 * <pre>
Value   Description
------  -------------------
 'n'     north
 's'     south
 'e'     east
 'w'     west
 'nw'    northwest
 'sw'    southwest
 'se'    southeast
 'ne'    northeast
 'all'   all
</pre>
 * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
 * <p>Here's an example showing the creation of a typical Resizer:</p>
 * <pre><code>
    <div id="elToResize" style="width:200px; height:100px; background-color:#000000;"></div>

    Ext.create('Ext.resizer.Resizer', {
        el: 'elToResize',
        handles: 'all',
        minWidth: 200,
        minHeight: 100,
        maxWidth: 500,
        maxHeight: 400,
        pinned: true
    });
</code></pre>
*/
Ext.define('Ext.resizer.Resizer', {
    mixins: {
        observable: 'Ext.util.Observable'
    },
    uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],

    alternateClassName: 'Ext.Resizable',

    handleCls: Ext.baseCSSPrefix + 'resizable-handle',
    pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
    overCls:   Ext.baseCSSPrefix + 'resizable-over',
    proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
    wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',

    /**
     * @cfg {Boolean} dynamic
     * <p>Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during dragging.
     * This is <code>true</code> by default, but the {@link Ext.Component Component} class passes <code>false</code> when it
     * is configured as {@link Ext.Component#resizable}.</p>
     * <p>If specified as <code>false</code>, a proxy element is displayed during the resize operation, and the {@link #target}
     * is updated on mouseup.</p>
     */
    dynamic: true,

    /**
     * @cfg {String} handles String consisting of the resize handles to display. Defaults to 's e se' for
     * Elements and fixed position Components. Defaults to 8 point resizing for floating Components (such as Windows).
     * Specify either <code>'all'</code> or any of <code>'n s e w ne nw se sw'</code>.
     */
    handles: 's e se',

    /**
     * @cfg {Number} height Optional. The height to set target to in pixels (defaults to null)
     */
    height : null,

    /**
     * @cfg {Number} width Optional. The width to set the target to in pixels (defaults to null)
     */
    width : null,

    /**
     * @cfg {Number} heightIncrement The increment to snap the height resize in pixels.
     * Defaults to <code>0</code>.
     */
    heightIncrement : 0,

    /**
     * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
     * Defaults to <code>0</code>.
     */
    widthIncrement : 0,

    /**
     * @cfg {Number} minHeight The minimum height for the element (defaults to 20)
     */
    minHeight : 20,

    /**
     * @cfg {Number} minWidth The minimum width for the element (defaults to 20)
     */
    minWidth : 20,

    /**
     * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
     */
    maxHeight : 10000,

    /**
     * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
     */
    maxWidth : 10000,

    /**
     * @cfg {Boolean} pinned True to ensure that the resize handles are always
     * visible, false indicates resizing by cursor changes only (defaults to false)
     */
    pinned: false,

    /**
     * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
     * and width during resize (defaults to false)
     */
    preserveRatio: false,

    /**
     * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
     */
    transparent: false,

    /**
     * @cfg {Mixed} constrainTo Optional. An element, or a {@link Ext.util.Region} into which the resize operation
     * must be constrained.
     */

    possiblePositions: {
        n:  'north',
        s:  'south',
        e:  'east',
        w:  'west',
        se: 'southeast',
        sw: 'southwest',
        nw: 'northwest',
        ne: 'northeast'
    },

    /**
     * @cfg {Mixed} target The Element or Component to resize.
     */

    /**
     * Outer element for resizing behavior.
     * @type Ext.core.Element
     * @property el
     */

    constructor: function(config) {
        var me = this,
            target,
            tag,
            handles = me.handles,
            handleCls,
            possibles,
            len,
            i = 0,
            pos;

        this.addEvents(
            /**
             * @event beforeresize
             * Fired before resize is allowed. Return false to cancel resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The start width
             * @param {Number} height The start height
             * @param {Ext.EventObject} e The mousedown event
             */
            'beforeresize',
            /**
             * @event resizedrag
             * Fires during resizing. Return false to cancel resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The new width
             * @param {Number} height The new height
             * @param {Ext.EventObject} e The mousedown event
             */
            'resizedrag',
            /**
             * @event resize
             * Fired after a resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The new width
             * @param {Number} height The new height
             * @param {Ext.EventObject} e The mouseup event
             */
            'resize'
        );

        if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
            target = config;
            config = arguments[1] || {};
            config.target = target;
        }
        // will apply config to this
        me.mixins.observable.constructor.call(me, config);

        // If target is a Component, ensure that we pull the element out.
        // Resizer must examine the underlying Element.
        target = me.target;
        if (target) {
            if (target.isComponent) {
                me.el = target.getEl();
                if (target.minWidth) {
                    me.minWidth = target.minWidth;
                }
                if (target.minHeight) {
                    me.minHeight = target.minHeight;
                }
                if (target.maxWidth) {
                    me.maxWidth = target.maxWidth;
                }
                if (target.maxHeight) {
                    me.maxHeight = target.maxHeight;
                }
                if (target.floating) {
                    if (!this.hasOwnProperty('handles')) {
                        this.handles = 'n ne e se s sw w nw';
                    }
                }
            } else {
                me.el = me.target = Ext.get(target);
            }
        }
        // Backwards compatibility with Ext3.x's Resizable which used el as a config.
        else {
            me.target = me.el = Ext.get(me.el);
        }

        // Tags like textarea and img cannot
        // have children and therefore must
        // be wrapped
        tag = me.el.dom.tagName;
        if (tag == 'TEXTAREA' || tag == 'IMG') {
            /**
             * Reference to the original resize target if the element of the original
             * resize target was an IMG or a TEXTAREA which must be wrapped in a DIV.
             * @type Mixed
             * @property originalTarget
             */
            me.originalTarget = me.target;
            me.target = me.el = me.el.wrap({
                cls: me.wrapCls,
                id: me.el.id + '-rzwrap'
            });

            // Transfer originalTarget's positioning/sizing
            me.el.setPositioning(me.originalTarget.getPositioning());
            me.originalTarget.clearPositioning();
            var box = me.originalTarget.getBox();
            me.el.setBox(box);
        }

        // Position the element, this enables us to absolute position
        // the handles within this.el
        me.el.position();
        if (me.pinned) {
            me.el.addCls(me.pinnedCls);
        }

        /**
         * @type Ext.resizer.ResizeTracker
         * @property resizeTracker
         */
        me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
            disabled: me.disabled,
            target: me.target,
            constrainTo: me.constrainTo,
            overCls: me.overCls,
            throttle: me.throttle,
            originalTarget: me.originalTarget,
            delegate: '.' + me.handleCls,
            dynamic: me.dynamic,
            preserveRatio: me.preserveRatio,
            heightIncrement: me.heightIncrement,
            widthIncrement: me.widthIncrement,
            minHeight: me.minHeight,
            maxHeight: me.maxHeight,
            minWidth: me.minWidth,
            maxWidth: me.maxWidth
        });

        // Relay the ResizeTracker's superclass events as our own resize events
        me.resizeTracker.on('mousedown', me.onBeforeResize, me);
        me.resizeTracker.on('drag', me.onResize, me);
        me.resizeTracker.on('dragend', me.onResizeEnd, me);

        if (me.handles == 'all') {
            me.handles = 'n s e w ne nw se sw';
        }

        handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
        possibles = me.possiblePositions;
        len = handles.length;
        handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';

        for(; i < len; i++){
            // if specified and possible, create
            if (handles[i] && possibles[handles[i]]) {
                pos = possibles[handles[i]];
                // store a reference in this.east, this.west, etc

                me[pos] = Ext.create('Ext.Component', {
                    owner: this,
                    region: pos,
                    cls: handleCls + pos,
                    renderTo: me.el
                });
                me[pos].el.unselectable();
                if (me.transparent) {
                    me[pos].el.setOpacity(0);
                }
            }
        }

        // Constrain within configured maxima
        if (Ext.isNumber(me.width)) {
            me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
        }
        if (Ext.isNumber(me.height)) {
            me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
        }

        // Size the element
        if (me.width != null || me.height != null) {
            if (me.originalTarget) {
                me.originalTarget.setWidth(me.width);
                me.originalTarget.setHeight(me.height);
            }
            me.resizeTo(me.width, me.height);
        }

        me.forceHandlesHeight();
    },

    disable: function() {
        this.resizeTracker.disable();
    },

    enable: function() {
        this.resizeTracker.enable();
    },

    /**
     * @private Relay the Tracker's mousedown event as beforeresize
     * @param tracker The Resizer
     * @param e The Event
     */
    onBeforeResize: function(tracker, e) {
        var b = this.target.getBox();
        return this.fireEvent('beforeresize', this, b.width, b.height, e);
    },

    /**
     * @private Relay the Tracker's drag event as resizedrag
     * @param tracker The Resizer
     * @param e The Event
     */
    onResize: function(tracker, e) {
        var me = this,
            b = me.target.getBox();
        me.forceHandlesHeight();
        return me.fireEvent('resizedrag', me, b.width, b.height, e);
    },

    /**
     * @private Relay the Tracker's dragend event as resize
     * @param tracker The Resizer
     * @param e The Event
     */
    onResizeEnd: function(tracker, e) {
        var me = this,
            b = me.target.getBox();
        me.forceHandlesHeight();
        return me.fireEvent('resize', me, b.width, b.height, e);
    },

    /**
     * Perform a manual resize and fires the 'resize' event.
     * @param {Number} width
     * @param {Number} height
     */
    resizeTo : function(width, height){
        this.target.setSize(width, height);
        this.fireEvent('resize', this, width, height, null);
    },

    /**
     * <p>Returns the element that was configured with the el or target config property.
     * If a component was configured with the target property then this will return the
     * element of this component.<p>
     * <p>Textarea and img elements will be wrapped with an additional div because
      * these elements do not support child nodes. The original element can be accessed
     * through the originalTarget property.</p>
     * @return {Element} element
     */
    getEl : function() {
        return this.el;
    },

    /**
     * <p>Returns the element or component that was configured with the target config property.<p>
     * <p>Textarea and img elements will be wrapped with an additional div because
      * these elements do not support child nodes. The original element can be accessed
     * through the originalTarget property.</p>
     * @return {Element/Component}
     */
    getTarget: function() {
        return this.target;
    },

    destroy: function() {
        var h;
        for (var i = 0, l = this.handles.length; i < l; i++) {
            h = this[this.possiblePositions[this.handles[i]]];
            delete h.owner;
            Ext.destroy(h);
        }
    },

    /**
     * @private
     * Fix IE6 handle height issue.
     */
    forceHandlesHeight : function() {
        var me = this,
            handle;
        if (Ext.isIE6) {
            handle = me.east;
            if (handle) {
                handle.setHeight(me.el.getHeight());
            }
            handle = me.west;
            if (handle) {
                handle.setHeight(me.el.getHeight());
            }
            me.el.repaint();
        }
    }
});

